//
// Created by zsl on 6/1/22.
//

#include "fishjoy/fiber/fiber.hpp"

#include <atomic>

#include "fishjoy/config/config.hpp"
#include "fishjoy/fiber/scheduler.hpp"
#include "fishjoy/log/log.hpp"
#include "fishjoy/utils/macro.hpp"

namespace fishjoy {

  static Logger::ptr g_logger = FISHJOY_LOG_NAME("system");
  
  /// 全局静态变量，用于生成协程id
  static std::atomic<uint64_t> s_fiber_id{0};
  /// 全局静态变量，用于统计当前的协程数
  static std::atomic<uint64_t> s_fiber_count{0};

  /// 线程局部变量，当前线程正在运行的协程
  static thread_local Fiber *t_fiber = nullptr;
  /// 线程局部变量，当前线程的主协程，切换到这个协程，就相当于切换到了主线程中运行，智能指针形式
  static thread_local Fiber::ptr t_thread_fiber = nullptr;

  ///协程栈大小，可通过配置文件获取，默认128k
  static ConfigVar<uint32_t>::ptr g_fiber_stack_size =
      Config::Lookup<uint32_t>("fiber.stack_size", 128 * 1024, "fiber stack size");

  /**
    * @brief malloc栈内存分配器
   */
  class MallocStackAllocator {
   public:
    static void* Alloc(size_t size) { return malloc(size); }

    static void Dealloc(void* vp, size_t size) { return free(vp); }
  };

  using StackAllocator = MallocStackAllocator;

  uint64_t Fiber::GetFiberId() {
    return t_fiber ? t_fiber->getId() : 0;
  }

  Fiber::Fiber() {
    SetThis(this);
    m_state = RUNNING;

    if (getcontext(&m_ctx)) {
      FISHJOY_ASSERT2(false, "getcontext");
    }

    ++s_fiber_count;
    m_id = s_fiber_id++; // 协程id从0开始，用完加1

    FISHJOY_LOG_DEBUG(g_logger) << "Fiber::Fiber() main id = " << m_id;
  }

  void Fiber::SetThis(Fiber *f) { 
    t_fiber = f; 
  }

  /**
    * 获取当前协程，同时充当初始化当前线程主协程的作用，这个函数在使用协程之前要调用一下
   */
  Fiber::ptr Fiber::GetThis() {
    if (t_fiber) {
      return t_fiber->shared_from_this();
    }

    Fiber::ptr main_fiber(new Fiber);
    FISHJOY_ASSERT(t_fiber == main_fiber.get());
    t_thread_fiber = main_fiber;
    return t_fiber->shared_from_this();
  }

  /**
    * 带参数的构造函数用于创建其他协程，需要分配栈
   */
  Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool run_in_scheduler)
      : m_id(s_fiber_id++)
        , m_callback(cb)
        , m_runInScheduler(run_in_scheduler) {
    ++s_fiber_count;
    m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();
    m_stack     = StackAllocator::Alloc(m_stacksize);

    if (getcontext(&m_ctx)) {
      FISHJOY_ASSERT2(false, "getcontext");
    }

    m_ctx.uc_link          = nullptr;
    m_ctx.uc_stack.ss_sp   = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;

    makecontext(&m_ctx, &Fiber::MainFunc, 0);

    FISHJOY_LOG_DEBUG(g_logger) << "Fiber::Fiber() id = " << m_id;
  }

  /**
 * 线程的主协程析构时需要特殊处理，因为主协程没有分配栈和cb
   */
  Fiber::~Fiber() {
    FISHJOY_LOG_DEBUG(g_logger) << "Fiber::~Fiber() id = " << m_id;
    --s_fiber_count;
    if (m_stack) {
      // 有栈，说明是子协程，需要确保子协程一定是结束状态
      FISHJOY_ASSERT(m_state == TERM);
      StackAllocator::Dealloc(m_stack, m_stacksize);
      FISHJOY_LOG_DEBUG(g_logger) << "dealloc stack, id = " << m_id;
    } else {
      // 没有栈，说明是线程的主协程
      FISHJOY_ASSERT(!m_callback);              // 主协程没有cb
      FISHJOY_ASSERT(m_state == RUNNING); // 主协程一定是执行状态

      Fiber *cur = t_fiber; // 当前协程就是自己
      if (cur == this) {
        SetThis(nullptr);
      }
    }
  }

  /**
 * 这里为了简化状态管理，强制只有TERM状态的协程才可以重置，但其实刚创建好但没执行过的协程也应该允许重置的
   */
  void Fiber::reset(std::function<void()> cb) {
    FISHJOY_ASSERT(m_stack);
    FISHJOY_ASSERT(m_state == TERM);
    m_callback = cb;
    if (getcontext(&m_ctx)) {
      FISHJOY_ASSERT2(false, "getcontext");
    }

    m_ctx.uc_link          = nullptr;
    m_ctx.uc_stack.ss_sp   = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;

    makecontext(&m_ctx, &Fiber::MainFunc, 0);
    m_state = READY;
  }

  void Fiber::resume() {
    FISHJOY_ASSERT(m_state != TERM && m_state != RUNNING);
    SetThis(this);
    m_state = RUNNING;

    // 如果协程参与调度器调度，那么应该和调度器的主协程进行swap，而不是线程主协程
    if (m_runInScheduler) {
      if (swapcontext(&(Scheduler::GetMainFiber()->m_ctx), &m_ctx)) {
        FISHJOY_ASSERT2(false, "swapcontext");
      }
    } else {
      if (swapcontext(&(t_thread_fiber->m_ctx), &m_ctx)) {
        FISHJOY_ASSERT2(false, "swapcontext");
      }
    }
  }

  void Fiber::yield() {
    /// 协程运行完之后会自动yield一次，用于回到主协程，此时状态已为结束状态
    FISHJOY_ASSERT(m_state == RUNNING || m_state == TERM);
    SetThis(t_thread_fiber.get());
    if (m_state != TERM) {
      m_state = READY;
    }

    // 如果协程参与调度器调度，那么应该和调度器的主协程进行swap，而不是线程主协程
    if (m_runInScheduler) {
      if (swapcontext(&m_ctx, &(Scheduler::GetMainFiber()->m_ctx))) {
        FISHJOY_ASSERT2(false, "swapcontext");
      }
    } else {
      if (swapcontext(&m_ctx, &(t_thread_fiber->m_ctx))) {
        FISHJOY_ASSERT2(false, "swapcontext");
      }
    }
  }

  /**
 * 这里没有处理协程函数出现异常的情况，同样是为了简化状态管理，并且个人认为协程的异常不应该由框架处理，应该由开发者自行处理
   */
  void Fiber::MainFunc() {
    Fiber::ptr cur = GetThis(); // GetThis()的shared_from_this()方法让引用计数加1
    FISHJOY_ASSERT(cur);

    cur->m_callback();
    cur->m_callback    = nullptr;
    cur->m_state = TERM;

    auto raw_ptr = cur.get(); // 手动让t_fiber的引用计数减1
    cur.reset();
    raw_ptr->yield();
  }

}